package checker

import (
	"kumachan/interpreter/lang/textual/ast"
	"kumachan/interpreter/lang/common/name"
	"kumachan/interpreter/lang/common/source"
	"kumachan/interpreter/compiler/loader"
	"kumachan/interpreter/compiler/checker/typsys"
	"kumachan/interpreter/compiler/checker/checked"
)


type Program struct {
	EntryPath  string
	Functions  [] ProgramFunction
	*ProgramInfo
}
type ProgramFunction struct {
	Name      name.FunctionName
	Body      FunctionBody
	IsThunk   bool
	IsEffect  bool
	Location  source.Location
}
type ProgramInfo struct {
	*Registry
	*ModuleInfo
	DispatchMapping
	*RpcInfo
}

func TypeCheck(entry *loader.Module) (*Program, source.Errors) {
	var mi, mic = collectModuleInfo(entry)
	var tr, tl, err1 = collectTypes(entry, mic)
	if err1 != nil { return nil, err1 }
	var fr, fl, dm, ri, err2 = collectFunctions(entry, mic, tr, tl)
	if err2 != nil { return nil, err2 }
	var reg = &Registry {
		Types:     tr,
		Functions: fr,
	}
	var info = &ProgramInfo {
		Registry:        reg,
		ModuleInfo:      mi,
		DispatchMapping: dm,
		RpcInfo:         ri,
	}
	var is_in_entry_mod = func(f *Function) bool {
		return (f.Name.ModuleName == mi.ModName)
	}
	var err3 = (func() source.Errors {
	var errs source.Errors
	var has_entry_effect = make(map[string] struct{})
	for _, f := range fl {
		if f.AstNode.Kind == ast.FK_Entry {
			var mod = f.Name.ModuleName
			var _, duplicate = has_entry_effect[mod]
			if duplicate {
				source.ErrorsJoin(&errs, source.MakeError(f.Location,
					E_DuplicateEntryEffect {}))
			} else {
				has_entry_effect[mod] = struct{}{}
			}
		}
	}
	return errs
	})()
	if err3 != nil { return nil, err3 }
	var functions = make([] ProgramFunction, len(fl))
	for i, f := range fl {
		var is_thunk = (f.AstNode.Kind == ast.FK_Constant)
		var is_effect = ((f.AstNode.Kind == ast.FK_Entry) && is_in_entry_mod(f))
		functions[i] = ProgramFunction {
			Name:     f.Name,
			Body:     f.Body,
			IsThunk:  is_thunk,
			IsEffect: is_effect,
			Location: f.Location,
		}
	}
	var program = &Program {
		EntryPath:   entry.Path,
		Functions:   functions,
		ProgramInfo: info,
	}
	return program, nil
}

func TypeCheckStandaloneExpr (
	expected  typsys.Type,
	node      ast.Expr,
	program   *Program,
	local     (map[string] *checked.LocalBinding),
) (*checked.Expr, *source.Error) {
	var ctx = ExprContext {
		ExprContextBase: &ExprContextBase {
			Registry:        program.Registry,
			ModuleInfo:      program.ModuleInfo,
			ParameterList:   [] typsys.Parameter {},
			DispatchMapping: program.DispatchMapping,
		},
		LocalBindingMap: local,
	}
	var expr, _, err = check(node)(expected, nil, ctx)
	return expr, err
}


